use crate::{
    ai::hint,
    board::Board,
    geometry::{Coord, Dir},
    history::History,
    moment::Moment,
    system::{BlockID, System},
    utils::{choose_by_weight, make_rng},
};
use either::{Either, Left, Right};
use rand_pcg::Pcg64Mcg;
use rand_seeder::Seeder;
use serde::{Deserialize, Serialize};
use std::{future::Future, pin::Pin};

pub struct Game<'a> {
    board: Board<'a, BlockID>,
    system: System,
    history: History<Board<'a, ()>>,
    rng: Either<Pcg64Mcg, u128>,
}

fn new_rng(seed: Option<u128>) -> Either<Pcg64Mcg, u128> {
    seed.map_or_else(|| Left(make_rng()), Right)
}

impl Game<'_> {
    /// 如果总面积为零，则返回空。
    pub fn new(size: Coord, seed: Option<u128>) -> Option<Self> {
        let mut game = Self {
            board: Board::new(size)?,
            system: System::new(size),
            history: History::new(),
            rng: new_rng(seed),
        };
        game.spawn();
        Some(game)
    }

    pub fn cont(&self) -> bool {
        self.board.cont()
    }

    pub fn set_max_history(&mut self, max_history: usize) {
        self.history.set_max(max_history);
    }

    /// 创建之后应当调用，不要多次调用
    pub async fn init(&mut self, stop: Pin<&mut impl Moment>) {
        self.show_score();
        self.system.run(true, stop).await;
    }

    pub async fn swipe<'a>(&'a mut self, dir: Dir, mut stop: Pin<&'a mut impl Moment>) {
        if self.board.swipe_valid(dir) {
            self.board.swipe(dir, &mut self.system);
            self.history.push(self.board.clone_to(&mut ()));
            self.system.run(false, stop.as_mut()).await;
            if self.system.spawn_effect() {
                self.system.run(true, stop.as_mut()).await;
            }
            self.system.do_remove();
            if self.spawn() {
                self.system.run(true, stop).await;
            }
            self.show_score();
        }
    }

    pub fn undo<'a>(
        &'a mut self,
        stop: Pin<&'a mut impl Moment>,
    ) -> Option<impl Future<Output = ()> + 'a> {
        let prev = self.history.pop()?;
        self.system.clear();
        self.board = prev.clone_to(&mut self.system);
        self.show_score();
        Some(self.system.run(true, stop))
    }

    pub async fn hint<'a>(&'a mut self, stop: Pin<&'a mut impl Moment>) {
        if let Some(dir) = hint(&self.board.clone_to(&mut ())) {
            self.swipe(dir, stop).await;
        }
    }

    fn show_score(&mut self) {
        self.system
            .show_score(self.board.score(), !self.board.cont());
    }

    fn spawn(&mut self) -> bool {
        let mut rng;
        let rng = match self.rng {
            Left(ref mut rng) => rng,
            Right(seed) => {
                rng = Seeder::from((seed, &self.board)).make_rng();
                &mut rng
            }
        };

        choose_by_weight(rng, self.board.possible_spawn())
            .map(|spawn| self.board.do_spawn(spawn, &mut self.system))
            .is_some()
    }
}

#[derive(Serialize, Deserialize)]
struct GameSerde<B, H> {
    board: B,
    size: Coord,
    history: H,
    seed: Option<u128>,
}

impl Serialize for Game<'_> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        GameSerde {
            board: &self.board,
            size: self.system.size(),
            history: &self.history,
            seed: self.rng.as_ref().right().copied(),
        }
        .serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for Game<'_> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let game = GameSerde::<Board<()>, _>::deserialize(deserializer)?;
        let mut system = System::new(game.size);
        Ok(Game {
            board: game.board.clone_to(&mut system),
            system,
            history: game.history,
            rng: new_rng(game.seed),
        })
    }
}
